Clicky

Util

Data Structures

Permutations

Using a depth first search this algorithm creates a list of all possible reordering of a list. It does this by recursively testing all options.

def permutations(collection: list) -> list:
    res = []
    collection.sort()
    cur = []
    def dfs(i):
        if cur not in res:
            res.append(cur.copy())
        if i >= len(collection):
            return
        cur.append(collection[i])
        dfs(i+1) # find all permutations with i

        cur.pop()
        dfs(i+1) # find all permutations without i
    dfs(0)

    return res

Equilibrium Index in Array

Find the index where the right and left sections of an array are equal in sum.

def equilibrium_index(arr: list) -> int:
    total_sum = sum(arr)
    left_sum = 0
    for i, value in enumerate(arr):
        total_sum -= value
        if left_sum == total_sum:
            return i
        left_sum += value
    return -1

Product Sum

Product sum of an array is the sum of the array multiplied by their depths

def product_sum(arr: list, depth: int) -> int:
    res = 0
    for x in arr:
        res += product_sum(x, depth+1) if isinstance(x, list) else x
    return res * depth

Find Triplets with 0 Sum

Finds three numbers that add up to zero by finding two and seeing if there exists a number that when added to them results in zero

def find_triplets_with_0_sum(arr: list) -> set:
    res = set()
    for i, item in enumerate(arr[:-2]):
        seen = set()
        for other in arr[i+1:]:
            to_find = -other-item
            if to_find in seen:
                res.add(tuple(sorted([item, other, to_find])))
            seen.add(other)
    return res

Kth Largest Element

Finds the position largest element in arr.

def kth_largest_element(arr: list, position: int) -> int:
    low, high = 0, len(arr) - 1

    def pivot_index():
        pivot = arr[high]
        i = low
        for j in range(low, high):
            if arr[j] >= pivot:
                arr[i], arr[j] = arr[j], arr[i]
                i += 1
        arr[i], arr[high] = arr[high], arr[i]
        return i

    while low <= high:
        if low > len(arr) - 1 or high < 0:
            return -1
        index = pivot_index()
        if index == position - 1:
            return arr[index]
        elif index > position - 1:
            high = index - 1
        else:
            low = index + 1

Binary Tree Node Sum

Sums up all nodes in a binary tree

from __future__ import annotations
from dataclasses import dataclass

@dataclass
class Node:
    value: int
    left: Node | None = None
    right: Node | None = None

def binary_tree_sum(tree: Node) -> int:
    def dfs(node):
        if node is None:
            return 0
        return node.value + dfs(node.left) + dfs(node.right)
    return dfs(tree)

Binary Tree Path Sum

Given a root and a target sum find the number of paths that sum up to the target

from __future__ import annotations
from dataclasses import dataclass

@dataclass
class Node:
    value: int
    left: Node | None = None
    right: Node | None = None

def binary_tree_path_sum(root: Node, target: int) -> int:
    res = 0

    def dfs(node, path_sum):
        nonlocal res
        if node is None:
            return
        if path_sum == target:
            res += 1
        if node.left:
            dfs(node.left, path_sum + node.left.value)
        if node.right:
            dfs(node.right, path_sum + node.right.value)

    def path_sum(node):
        if node is None:
            return
        dfs(node, node.value)
        path_sum(node.left)
        path_sum(node.right)

    path_sum(root)

    return res

Diameter Of Binary Tree

Finds the diameter of a binary tree where the diameter is defined as the number of nodes on the longest path between two end nodes

from __future__ import annotations
from dataclasses import dataclass

@dataclass
class Node:
    value: int
    left: Node | None = None
    right: Node | None = None

def dimeter_of_binary_tree(root: Node) -> int:
    def depth(node):
        if not node:
            return 0
        return max(depth(node.left), depth(node.right)) + 1
    res = 0
    if root.left:
        res += depth(root.left)
    if root.right:
        res += depth(root.right)
    return res + 1

Prime

Lists all prime numbers < n

def list_primes(n: int) -> list:
    primes = []
    if n > 1:
        primes.append(1)
    if n > 2:
        primes.append(2)
    numbers = (i for i in range(1, (n+1), 2))
    for i in (n for n in numbers if n > 1):
        bound = int(i**0.5) + 1
        for j in range(3, bound, 2):
            if i % j == 0:
                break
        else:
            primes.append(i)
    return primes

Binary Tree Mirror

Return the mirror of a binary tree, flipping it around the root node.

from __future__ import annotations
from dataclasses import dataclass

@dataclass
class Node:
    value: int
    left: Node | None = None
    right: Node | None = None

def mirror_binary_tree(root: Node) -> Node:
    def mirror(node):
        if not node:
            return
        node.left, node.right = node.right, node.left
        mirror(node.left)
        mirror(node.right)
    mirror(root)
    return root

Different Views of a Binary Tree

If you were to look at the binary tree from a certain perspective what would you see

from __future__ import annotations
from collections import defaultdict, deque
from dataclasses import dataclass

@dataclass
class Node:
    value: int
    left: Node | None = None
    right: Node | None = None

def binary_tree_side_view(root: Node, right: bool) -> list:
    res = []
    def dfs(node, depth):
        if not node:
            return
        if depth == len(res):
            res.append(node.value)
        if right:
            dfs(node.right, depth+1)
            dfs(node.left, depth+1)
        else:
            dfs(node.left, depth+1)
            dfs(node.right, depth+1)
    dfs(root, 0)
    return res

def binary_tree_top_and_bottom_view(root: Node, top: bool) -> list:
    queue = deque([(root, 0)])
    seen = defaultdict(list)
    res = []
    while queue:
        node, order = queue.pop()

        seen[order].append(node.value)

        if node.left:
            queue.append((node.left, order-1))
        if node.right:
            queue.append((node.right, order+1))

    for _, values in sorted(seen.items(), key=lambda x: x[0]):
        if top:
            res.append(values[0])
        else:
            res.append(values[-1])

    return res

Distribute Coins

Finds number of moves it would take for each node if the binary tree to have a value of one if you can only move one coin at a time

from __future__ import annotations
from dataclasses import dataclass

@dataclass
class Node:
    value: int
    left: Node | None = None
    right: Node | None = None

def distribute_coins(root: Node):

    def distribute(node):
        if node is None:
            return (0, 1)

        left_moves, left_excess = distribute(node.left)
        right_moves, right_excess = distribute(node.right)

        coins_to_left = 1 - left_excess
        coins_to_right = 1 - right_excess

        new_moves = left_moves + right_moves + abs(coins_to_left) + abs(coins_to_right)

        return (new_moves, node.value - coins_to_left - coins_to_right)

    return distribute(root)[0]

Flatten Binary Tree to LinkedList

Takes a binary tree and compresses it in-place into a linked list

from __future__ import annotations
from dataclasses import dataclass

@dataclass
class Node:
    value: int
    left: Node | None = None
    right: Node | None = None

def binary_tree_to_linked_list(root: Node) -> None:
    def flatten(node):
        if node is None:
            return
        flatten(node.left)
        right = node.right
        node.right = node.left
        node.left = None
        cur = node
        while cur.right:
            cur = cur.right
        
        cur.right = right
        flatten(right)
    flatten(root)

Is Binary Tree Sorted

Checks if a binary tree is sorted, i.e. is it a valid BST, binary search tree.

from __future__ import annotations
from dataclasses import dataclass

@dataclass
class Node:
    value: int
    left: Node | None = None
    right: Node | None = None

def is_sorted(root: Node) -> bool:
    if root.left and (root.value < root.left.value or not is_sorted(root.left)):
        return False
    if root.right and (root.value > root.right.value or not is_sorted(root.right)):
        return False
    return True

Is Sum Tree

Checks if a binary tree is a sum tree.

from __future__ import annotations
from dataclasses import dataclass

@dataclass
class Node:
    value: int
    left: Node | None = None
    right: Node | None = None

    def __iter__(self):
        if self.left:
            yield from self.left
        yield self.value
        if self.right:
            yield from self.right

def is_sum_tree(root: Node) -> bool:
    if not root.left and not root.right:
        return True
    left = sum(root.left) if root.left else 0
    right = sum(root.right) if root.right else 0
    return all((root.value == left+right, 
                is_sum_tree(root.left) if root.left else True, 
                is_sum_tree(root.right) if root.right else True))

Merge Two Binary Trees

Combines two binary trees, if two node overlap the value is set to their sum otherwise it is set to the non null node.

from __future__ import annotations
from dataclasses import dataclass

@dataclass
class Node:
    value: int
    left: Node | None = None
    right: Node | None = None

def merge_two_binary_trees(root: Node, other: Node) -> Node:
    if root is None:
        return other
    if other is None:
        return root

    root.value = root.value + other.value
    root.left = merge_two_binary_trees(root.left, other.left)
    root.right = merge_two_binary_trees(root.right, other.right)
    return root

Number Of Possible Binary Trees

Finds number of possible binary trees given a number of nodes.

def number_of_binary_trees(nodes: int) -> int:
    def factorial(n):
        out = 1
        for i in range(1, n+1):
            out *= i
        return out
    
    def catalan_number(n):
        out = 1
        for i in range(n):
            out *= 2*n-i
            out //= i+1
        return out // (n+1)
        

    return factorial(nodes) * catalan_number(nodes)

Floyds Cycle Detection

Detects cycles in a list using a two pointer system (fast and slow pointer), if a linked list has a cycle then the fast pointer will loop back around and catch up to the slow pointer.

from __future__ import annotations
from dataclasses import dataclass

@dataclass
class Node:
    data: any
    next_node: Node | None = None

def detect_cycle(head: Node) -> bool:
    if not head:
        return False

    slow = head
    fast = head
    while fast and fast.next_node:
        slow = slow.next_node if slow else None
        fast = fast.next_node.next_node
        if slow == fast:
            return True

    return False

Middle Element of Linked List

Finds the middle element of a linked list.

from __future__ import annotations
from dataclasses import dataclass

@dataclass
class Node:
    data: any
    next_node: Node | None = None

def find_middle_element(head: Node) -> int:
    slow = head
    fast = head

    while fast and fast.next_node:
        fast = fast.next_node.next_node
        slow = slow.next_node
    return slow.data

Rotate To The Right

Rotates the linked list to the right a number of places

from __future__ import annotations
from dataclasses import dataclass

@dataclass
class Node:
    data: any
    next_node: Node | None = None

def rotate(head: Node, places: int) -> Node:
    if not head.next_node:
        return head

    length = 1
    temp = head
    while temp.next_node:
        length += 1
        temp = temp.next_node

    new_head_index = length-places

    temp = head
    for _ in range(new_head_index-1):
        temp = temp.next_node
    
    new_head = temp.next_node
    temp.next_node = None
    temp = new_head
    while temp.next_node:
        temp = temp.next_node
    temp.next_node = head

    return new_head

Swap Nodes

Swaps the data values of two nodes in a linked list

from __future__ import annotations
from dataclasses import dataclass

@dataclass
class Node:
    data: any
    next_node: Node | None = None

def swap_nodes(head:Node, node_data_1: int, node_data_2: int) -> Node:
    node_1 = head
    while node_1 and node_1.data != node_data_1:
        node_1 = node_1.next_node
    node_2 = head
    while node_2 and node_2.data != node_data_2:
        node_2 = node_2.next_node
    node_1.data, node_2.data = node_2.data, node_1.data
    return head

Balanced Parentheses

Are the parentheses balanced

def are_balanced_parentheses(parentheses: str) -> bool:
    stack = []
    bracket_pairs = {"(": ")", "[": "]", "{": "}"}
    for bracket in parentheses:
        if bracket in bracket_pairs.keys():
            stack.append(bracket)
        elif bracket in bracket_pairs.values() and (len(stack) == 0 or
            bracket_pairs[stack.pop()] != bracket):
            return False
    return len(stack) == 0

Dijkstras Two Stack Algorithm

Algorithm so solve an equation such as ((5 + 3) * 6). Parenthesis are required.

import operator as op

def solve_equation(equation: str) -> float:
    operators = {"*": op.mul, "/": op.truediv, "+": op.add, "-": op.sub}

    operand_stack = []
    operator_stack = []

    for ch in equation:
        if ch.isdigit():
            operand_stack.append(int(ch))
        elif ch in operators:
            operator_stack.append(ch)
        elif ch == ")":
            operator = operator_stack.pop()
            number_1 = operand_stack.pop()
            number_2 = operand_stack.pop()

            total = operators[operator](number_1, number_2)
            operand_stack.append(total)
            
    return operand_stack.pop()

Infix to Postfix Conversion

Convert an infix operation to a postfix operation.

def infix_to_postfix(expression):
    stack = []
    postfix = []

    precedences = ["+","-","*","/","^"]

    for char in expression:
        if char.isalpha() or char.isdigit():
            postfix.append(char)
        elif char == "(":
            stack.append(char)
        elif char == ")":
            while stack and stack[-1] != "(":
                postfix.append(stack.pop())
            stack.pop()
        else:
            while True:
                if not stack:
                    stack.append(char)
                    break

                char_precedence = precedences.index(char)
                tos_precedence = precedences.index(stack[-1]) if stack[-1] in precedences else -1

                if char_precedence > tos_precedence:
                    stack.append(char)
                    break
                if char_precedence < tos_precedence:
                    postfix.append(stack.pop())
                    continue

                if char == "^":
                    stack.append(char)
                    break
                postfix.append(stack.pop())

    postfix.extend(stack)
    return ' '.join(postfix)

Next Greater Element

Finds the next greater element for each index -1 if element can’t be found.

def next_greatest_element(arr: list) -> list:
    stack = []
    result = [-1] * len(arr)
    for i in reversed(range(len(arr))):
        if stack:
            while stack[-1] <= arr[i]:
                stack.pop()
                if not stack:
                    break
        if stack:
            result[i] = stack[-1]
        stack.append(arr[i])
    return result

Postfix Evaluation

Evaluates a postfix equation.

def evaluate(expressions: list[str]) -> float:
    stack = []

    operators = {
        "^": lambda a, b: a**b,
        "*": lambda a, b: a * b,
        "/": lambda a, b: a / b,
        "+": lambda a, b: a + b,
        "-": lambda a, b: a - b,
    }
    for expression in expressions:
        if expression not in operators:
            stack.append(float(expression))
            continue
        if expression in ["-","+"] and len(stack) < 2:
            x = stack.pop()
            if expression == "-":
                x *= -1
            stack.append(float(x))
            continue
        b = stack.pop()
        a = stack.pop()
        stack.append(operators[expression](a,b))
    return float(stack[0])

Stock Span Calculation

def calculate_span(prices: list, span: list) -> list:
    stack = [0]
    span[0] = 1
    for price in range(1, len(prices)):
        while stack and prices[stack[0]] <= prices[price]:
            stack.pop()
        
        span[price] = price + (-stack[0] if stack else 1)

        stack.append(price)

    return span